/* XXX Custom detection.  Below is just for generation of base code. XXX
alert udp $HOME_NET any -> $EXTERNAL_NET 53 (msg:"BAD-TRAFFIC dns query - storing query and txid"; flow:to_server; content:"|00 01 00 00 00 00 00 00|"; offset:4; depth:8; flowbits:noalert; classtype:misc-activity; sid:21354;)
alert udp $EXTERNAL_NET 53 -> $HOME_NET any (msg:"BAD-TRAFFIC dns cache poisoning attempt - mismatched txid"; flow:to_client; content:"|00 01 00 01|"; offset:4; depth:4; classtype:attempted-recon; sid:21355;)
*/
/*

Description of what we're trying to do here --

This is going to be a crazy pair of rules, but I think this will actually
replace our TXID spoofing rule (13667) as it won't care what type of record
it's spoofing.

Okay, in GLOBAL data, we have a table.  It needs to be global because we're
checking across multiple streams.  That table will include NUM_RECORDS records.
 We'll try 256 and see how that goes.  Each record will contain the following
information:

typedef struct {
   uint16_t txid,
   uint8_t querylen,
   const uint8_t query[256]
} dns_query_record;

Okay, so the first rule will watch queries.  The content match will be "|00 01
00 00 00 00 00 00|"; depth:8; offset:4;  This will give us 1 question, 0
answers, auth, additional records.  Then, we will verify we are looking at a
standard query by checking the third byte to bitmask to 00000x0x.  The fourth
byte of the packet (the second byte of the flags) we don't care about the value
so we'll ignore it.  If everything checks out okay, we will store the query,
which is every byte after the content match until the end of the payload.  We
will truncate at 256 bytes if necessary.  Store the stored length in the
structure.

The second rule will watch responses.  We are looking for a response to the
same query with a different TXID.  Now, this could false positive if the server
is non-caching and multiple people ask for the same record and an old query 
response is received after the query has dropped off our cache but the new
query is still present in our cache.  Anyway, here's the logic for this second
rule -- content match on "|00 01 00 01|" offset 4, depth 4 for 1 question, 1
answer.  Don't care about auth or add'l records.  Verify it's a response by
checking third byte to bitmask to 10000x0x.  Now, for each element in the
table, do a content match.  If it matches, check the response's txid with the
one from the table.  If we find matches for the query but no matching
query with a matching txid, alert.

*/
/*
 * Vuln Title: XXXX
 *
 * Copyright (C) 2005-2010 Sourcefire, Inc. All Rights Reserved
 *
 * Written by XXXX, Sourcefire VRT <XXXX@sourcefire.com>
 *
 * Auto-generated by XXXX
 *
 * This file may contain proprietary rules that were created, tested and
 * certified by Sourcefire, Inc. (the "VRT Certified Rules") as well as
 * rules that were created by Sourcefire and other third parties and
 * distributed under the GNU General Public License (the "GPL Rules").  The
 * VRT Certified Rules contained in this file are the property of
 * Sourcefire, Inc. Copyright 2005 Sourcefire, Inc. All Rights Reserved.
 * The GPL Rules created by Sourcefire, Inc. are the property of
 * Sourcefire, Inc. Copyright 2002-2005 Sourcefire, Inc. All Rights
 * Reserved.  All other GPL Rules are owned and copyrighted by their
 * respective owners (please see www.snort.org/contributors for a list of
 * owners and their respective copyrights).  In order to determine what
 * rules are VRT Certified Rules or GPL Rules, please refer to the VRT
 * Certified Rules License Agreement.
 */

#include "sf_snort_plugin_api.h"
#include "sf_snort_packet.h"

#include "so-util.h"

#include <string.h>

#define DQR_NUM_DNS_ENTRIES 64 // WAG at a good balance between mildly used server and FP avoidance
#define DQR_MAX_QUERY_LEN 256
static struct dns_query_record {
   uint16_t txid;
   uint16_t querylen;
   const uint8_t query[DQR_MAX_QUERY_LEN];
} dqr_dns_query_table[DQR_NUM_DNS_ENTRIES];

static uint8_t dqr_init = 0;
static uint32_t dqr_index = 0;
static int max_dqr_index = -1;

//#define DEBUG 
#ifdef DEBUG
#define DEBUG_SO(code) code
#else
#define DEBUG_SO(code)
#endif

/* declare detection functions */
int rule21354eval(void *p);
int rule21355eval(void *p);

/* declare rule data structures */
/* flow:to_server; */
static FlowFlags rule21354flow0 = 
{
    FLOW_TO_SERVER
};

static RuleOption rule21354option0 =
{
    OPTION_TYPE_FLOWFLAGS,
    {
        &rule21354flow0
    }
};
// content:"|00 01 00 00 00 00 00 00|", offset 4, depth 8, fast_pattern; 
static ContentInfo rule21354content1 = 
{
    (uint8_t *) "|00 01 00 00 00 00 00 00|", /* pattern (now in snort content format) */
    8, /* depth */
    4, /* offset */
    CONTENT_FAST_PATTERN|CONTENT_BUF_NORMALIZED, /* flags */
    NULL, /* holder for boyer/moore PTR */
    NULL, /* more holder info - byteform */
    0, /* byteform length */
    0 /* increment length*/
};

static RuleOption rule21354option1 = 
{
    OPTION_TYPE_CONTENT,
    {
        &rule21354content1
    }
};

/* references for sid 21354 */
static RuleReference rule21354ref1 =
{
    "cve", /* type */
    "2010-1690" /* value */
};

/* reference: url "technet.microsoft.com/en-us/security/bulletin/MS10-024"; */
static RuleReference rule21354ref2 =
{
    "url", /* type */
    "technet.microsoft.com/en-us/security/bulletin/MS10-024" /* value */
};

static RuleReference *rule21354refs[] =
{
    &rule21354ref1,
    &rule21354ref2,
    NULL
};

/* metadata for sid 21354 */
/* metadata:policy max-detect-ips drop; */
static RuleMetaData rule21354policy0 =
{
   "policy max-detect-ips drop"
};

static RuleMetaData *rule21354metadata[] =
{
   &rule21354policy0,
    NULL
};

RuleOption *rule21354options[] =
{
    &rule21354option0,
    &rule21354option1,
    NULL
};

Rule rule21354 = {
   /* rule header, akin to => tcp any any -> any any */
   {
       IPPROTO_UDP, /* proto */
       "$HOME_NET", /* SRCIP     */
       "any", /* SRCPORT   */
       0, /* DIRECTION */
       "$EXTERNAL_NET", /* DSTIP     */
       "53", /* DSTPORT   */
   },
   /* metadata */
   { 
       3,  /* genid */
       21354, /* sigid */
       5, /* revision */
       "misc-activity", /* classification */
       0,  /* hardcoded priority XXX NOT PROVIDED BY GRAMMAR YET! */
       "PROTOCOL-DNS dns query - storing query and txid",     /* message */
       rule21354refs /* ptr to references */
       ,rule21354metadata
   },
   rule21354options, /* ptr to rule options */
   &rule21354eval, /* use the built in detection function */
   0 /* am I initialized yet? */
};
/* flow:to_client; */
static FlowFlags rule21355flow0 = 
{
    FLOW_TO_CLIENT
};

static RuleOption rule21355option0 =
{
    OPTION_TYPE_FLOWFLAGS,
    {
        &rule21355flow0
    }
};
// content:"|00 01 00 01|", offset 4, depth 4, fast_pattern; 
static ContentInfo rule21355content1 = 
{
    (uint8_t *) "|00 01 00 01|", /* pattern (now in snort content format) */
    4, /* depth */
    4, /* offset */
    CONTENT_FAST_PATTERN|CONTENT_BUF_NORMALIZED, /* flags */
    NULL, /* holder for boyer/moore PTR */
    NULL, /* more holder info - byteform */
    0, /* byteform length */
    0 /* increment length*/
};

static RuleOption rule21355option1 = 
{
    OPTION_TYPE_CONTENT,
    {
        &rule21355content1
    }
};

RuleOption *rule21355options[] =
{
    &rule21355option0,
    &rule21355option1,
    NULL
};

Rule rule21355 = {
   /* rule header, akin to => tcp any any -> any any */
   {
       IPPROTO_UDP, /* proto */
       "$EXTERNAL_NET", /* SRCIP     */
       "53", /* SRCPORT   */
       0, /* DIRECTION */
       "$HOME_NET", /* DSTIP     */
       "any", /* DSTPORT   */
   },
   /* metadata */
   { 
       3,  /* genid */
       21355, /* sigid */
       5, /* revision */
       "attempted-recon", /* classification */
       0,  /* hardcoded priority XXX NOT PROVIDED BY GRAMMAR YET! */
       "PROTOCOL-DNS potential dns cache poisoning attempt - mismatched txid",     /* message */
       rule21354refs /* ptr to references */
       ,rule21354metadata
   },
   rule21355options, /* ptr to rule options */
   &rule21355eval, /* use the built in detection function */
   0 /* am I initialized yet? */
};

#ifdef DEBUG
void print_dns_name(const uint8_t *entry) {
   uint8_t seglen;

   int i, j;

   i = 0;
   while(entry[i] != 0) {
      seglen = entry[i++];
      if((seglen & 0xC0) == 0xC0) { // is a pointer
         printf("<p>");
         break;
      }

      for(j = 0; j < seglen; j++) {
         printf("%c", entry[i++]);
      }

      if(entry[i] != 0) // No increment here; sets seglen above
         printf(".");
      else
         break; // Redundant with loop conditional.  :shrug:
   };
}
#endif

/* detection functions */
int rule21354eval(void *p) {
   const uint8_t *cursor_normal = 0;
   SFSnortPacket *sp = (SFSnortPacket *) p;

   const uint8_t *beg_of_payload, *end_of_payload;

   uint8_t flags, txid;
   uint32_t querylen;

   DEBUG_SO(printf("rule21354eval enter\n");)

   if(sp == NULL)
      return RULE_NOMATCH;

   if(sp->payload == NULL)
      return RULE_NOMATCH;
   
   // flow:to_server;
   if(checkFlow(p, rule21354options[0]->option_u.flowFlags) <= 0)
      return RULE_NOMATCH;

   // 1 query, 0 answers, 0 auth, 0 addl
   // content:"|00 01 00 00 00 00 00 00|", offset 4, depth 8, fast_pattern;
   if(contentMatch(p, rule21354options[1]->option_u.content, &cursor_normal) <= 0)
      return RULE_NOMATCH;

   if(getBuffer(sp, CONTENT_BUF_NORMALIZED, &beg_of_payload, &end_of_payload) <= 0)
      return RULE_NOMATCH;
 
   // Check the flags
   flags = beg_of_payload[2];
   if((flags & 0xFA) != 0x00) // Standard query, not truncated
      return RULE_NOMATCH;

   DEBUG_SO(printf("found standard query\n");)

   // Check for validity of the table
   if(dqr_init == 0) {
      DEBUG_SO(printf("Clearing dqr_dns_query_table\n");)
      memset(dqr_dns_query_table, 0x00, sizeof(dqr_dns_query_table));
      dqr_index = 0;
      dqr_init = 1;
   }

   // Store the query
   txid = read_big_16(beg_of_payload);
   querylen = end_of_payload - cursor_normal;

   if(querylen == 0) // No data, don't save
      return RULE_NOMATCH;

   // If our query data is too big, truncate and call it good enough
   if(querylen > DQR_MAX_QUERY_LEN)
      querylen = DQR_MAX_QUERY_LEN;

   dqr_dns_query_table[dqr_index].txid = txid;
   dqr_dns_query_table[dqr_index].querylen = querylen;
   memcpy((void*)dqr_dns_query_table[dqr_index].query, cursor_normal, querylen);
   DEBUG_SO(printf("Stored %d bytes, dns name is ", querylen);)
   DEBUG_SO(print_dns_name(dqr_dns_query_table[dqr_index].query);)
   DEBUG_SO(printf(", txid 0x%04x\n", txid);)

   // Round robin storage
   dqr_index++;
   if(dqr_index >= DQR_NUM_DNS_ENTRIES)
      dqr_index = 0;

   if(max_dqr_index < (int)dqr_index)
      max_dqr_index = dqr_index;

   // flowbits:noalert
   return RULE_NOMATCH;
}


int rule21355eval(void *p) {
   const uint8_t *cursor_normal = 0;
   SFSnortPacket *sp = (SFSnortPacket *) p;

   const uint8_t *beg_of_payload, *end_of_payload, *start_of_query;

   uint32_t query_data_avail;
   uint8_t flags, txid, found_match;

   int i;

   DEBUG_SO(printf("rule21355eval enter\n");)

   if(sp == NULL)
      return RULE_NOMATCH;

   if(sp->payload == NULL)
      return RULE_NOMATCH;

   if(dqr_init == 0) {
      DEBUG_SO(printf("dqr_dns_query_table is not initialized.  Exiting.\n");)
      return RULE_NOMATCH;
   }
   
   // flow:to_client;
   if(checkFlow(p, rule21355options[0]->option_u.flowFlags) <= 0)
      return RULE_NOMATCH;

   // 1 query, 1 answer
   // content:"|00 01 00 01|", offset 4, depth 4, fast_pattern;
   if(contentMatch(p, rule21355options[1]->option_u.content, &cursor_normal) <= 0)
      return RULE_NOMATCH;

   if(getBuffer(sp, CONTENT_BUF_NORMALIZED, &beg_of_payload, &end_of_payload) <= 0)
      return RULE_NOMATCH;

   // Check the flags
   flags = beg_of_payload[2];
   DEBUG_SO(printf("flags: 0x%04x\n", flags);)
   if((flags & 0xFA) != 0x80) // Standard response, not truncated
      return RULE_NOMATCH;

   DEBUG_SO(printf("found standard response.\n");)

   start_of_query = beg_of_payload + 12; // 12 bytes of header info

   if(start_of_query >= end_of_payload)
      return RULE_NOMATCH;

   query_data_avail = end_of_payload - start_of_query;

   // Extract the TXID for checking within the loop
   txid = read_big_16(beg_of_payload);

   found_match = 0;
   DEBUG_SO(printf("max_dqr_index = %d\n", max_dqr_index);)
   for(i = 0; i <= max_dqr_index; i++) {

      DEBUG_SO(printf("Checking dns entry %d\n", i);)

      // See if we can find the related query for this response
      if(dqr_dns_query_table[i].querylen > query_data_avail)
         continue; // Not enough data in this packet for this query; try the next one

      if(memcmp(start_of_query, dqr_dns_query_table[i].query, dqr_dns_query_table[i].querylen) == 0) {
         // If we get a match, check the TXID.  If they match, return RULE_NOMATCH.
         // If they don't match, set found_match and keep going because we might
         // find a matching TXID from another query packet.

         DEBUG_SO(printf("Found match for query ");)
         DEBUG_SO(print_dns_name(dqr_dns_query_table[i].query);)
         DEBUG_SO(printf(", ");)
         DEBUG_SO(print_dns_name(start_of_query);)
         DEBUG_SO(printf(".  Table txid 0x%04x, packet txid 0x%04x\n", dqr_dns_query_table[i].txid, txid);)

         if(txid == dqr_dns_query_table[i].txid) {
            DEBUG_SO(printf("txid in packet matches txid in table.  Valid response.\n");)
            return RULE_NOMATCH;
         }

         found_match = 1;
      } 
   }

   // If we got here, we didn't find a query with a matching txid.
   // If we found a matching query, then alert because the txid was wrong.
   if(found_match) {
      DEBUG_SO(printf("Found a match on the query, but no matching txid.  Alerting.\n");)
      return RULE_MATCH;
   }

   return RULE_NOMATCH;
}

/*
Rule *rules[] = {
    &rule21354,
    &rule21355,
    NULL
};
*/
